Conversation
# Conflicts: # src/Nethermind/Nethermind.Evm/VirtualMachine.cs
Contributor
Author
|
Current implementation uses built-in .NET ECDsa, which is not the fastest option. Benchmarks comparing different versions: .NETWindows 10
Ubuntu 22
GoWindows 10
Ubuntu 22
|
Member
|
Which go lib specifically is that? |
Contributor
Author
@benaadams, built-in ecdsa package. Go code used: import (
"encoding/hex"
"crypto/ecdsa"
"crypto/elliptic"
"math/big"
"fmt"
"C"
)
//export VerifyHex
func VerifyHex(input *C.char) bool {
var h = C.GoString(input)
bytes, err := hex.DecodeString(h)
if err != nil {
return false
}
return VerifyBytes(bytes)
}
//export VerifyBytes
func VerifyBytes(bytes []byte) bool {
if len(bytes) != 160 {
return false
}
var hash = bytes[0:32]
var r, s = new(big.Int).SetBytes(bytes[32:64]), new(big.Int).SetBytes(bytes[64:96])
var x, y = new(big.Int).SetBytes(bytes[96:128]), new(big.Int).SetBytes(bytes[128:160])
var publicKey = &ecdsa.PublicKey{Curve: elliptic.P256(), X: x, Y: y}
return ecdsa.Verify(publicKey, hash, r, s)
}Can be built into a library via go build -ldflags="-s -w" -buildmode=c-shared -o secp256r1.dll main.go |
Contributor
Author
|
C# benchmarking code: public class Secp256r1VerifyBenchmarks
{
private const string InputHex = "4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e";
private static readonly byte[] Input = Convert.FromHexString(InputHex);
private ECDsa _ecdsaShared;
private X9ECParameters _bouncyCurve;
private ISigner _bouncySigner;
private ECDomainParameters _bouncyDomainParameters;
[GlobalSetup]
public void Setup()
{
_ecdsaShared = ECDsa.Create();
_bouncyCurve = CustomNamedCurves.GetByName("secp256r1");
_bouncySigner = SignerUtilities.GetSigner("NONEWITHPLAIN-ECDSA");
_bouncyDomainParameters = new(_bouncyCurve);
}
[Benchmark]
public bool DotNet_Ecdsa_WithCreate()
{
ReadOnlySpan<byte> input = Input;
ReadOnlySpan<byte> hash = input[..32];
ReadOnlySpan<byte> sig = input[32..96];
ReadOnlySpan<byte> x = input[96..128];
ReadOnlySpan<byte> y = input[128..160];
var ecdsa = ECDsa.Create(new ECParameters
{
Curve = ECCurve.NamedCurves.nistP256,
Q = new() { X = x.ToArray(), Y = y.ToArray() }
});
return ecdsa.VerifyHash(hash, sig);
}
[Benchmark]
public bool DotNet_Ecdsa_WithImportParameters()
{
ReadOnlySpan<byte> input = Input;
ReadOnlySpan<byte> hash = input[..32];
ReadOnlySpan<byte> sig = input[32..96];
ReadOnlySpan<byte> x = input[96..128];
ReadOnlySpan<byte> y = input[128..160];
ECDsa ecdsa = _ecdsaShared;
ecdsa.ImportParameters(new ECParameters
{
Curve = ECCurve.NamedCurves.nistP256,
Q = new() { X = x.ToArray(), Y = y.ToArray() }
});
return ecdsa.VerifyHash(hash, sig);
}
[Benchmark] // 211.2 us
public bool BouncyCastle()
{
ReadOnlySpan<byte> input = Input;
ReadOnlySpan<byte> hash = input[..32];
ReadOnlySpan<byte> sig = input[32..96];
ReadOnlySpan<byte> x = input[96..128];
ReadOnlySpan<byte> y = input[128..160];
ECPublicKeyParameters parameters = new(
"ECDSA",
_bouncyCurve.Curve.CreatePoint(new Org.BouncyCastle.Math.BigInteger(1, x), new Org.BouncyCastle.Math.BigInteger(1, y)),
_bouncyDomainParameters
);
_bouncySigner.Init(false, parameters);
_bouncySigner.BlockUpdate(hash);
return _bouncySigner.VerifySignature(sig.ToArray());
}
#if Windows
[DllImport("secp256r1.dll", CallingConvention = CallingConvention.Cdecl)]
#elif Linux
[DllImport("secp256r1.so", CallingConvention = CallingConvention.Cdecl)]
#endif
private static extern byte VerifyHex(string hash);
[Benchmark]
public bool Go_Ecdsa_MarshallingString() => VerifyHex(InputHex) != 0;
#if Windows
[DllImport("secp256r1.dll", CallingConvention = CallingConvention.Cdecl)]
#elif Linux
[DllImport("secp256r1.so", CallingConvention = CallingConvention.Cdecl)]
#endif
private static extern byte VerifyBytes(GoSlice hash);
private struct GoSlice(IntPtr data, long len, long cap)
{
public IntPtr Data = data;
public long Len = len, Cap = cap;
}
[Benchmark]
public bool Go_Ecdsa_MarshallingSlice()
{
unsafe
{
fixed (byte* p = Input)
{
var ptr = (IntPtr) p;
var slice = new GoSlice(ptr, Input.Length, Input.Length);
return VerifyBytes(slice) != 0;
}
}
}
// Other options:
// https://github.com/starkbank/ecdsa-dotnet - needs bridge/pinvoke from Rust
// https://github.com/MystenLabs/fastcrypto - very slow actually
} |
smartprogrammer93
approved these changes
Jun 12, 2024
flcl42
approved these changes
Jun 13, 2024
LukaszRozmej
approved these changes
Jun 13, 2024
Member
|
Aside; this is different to the |
Contributor
Author
|
It's for secP256_r_1 |
Contributor
Author
|
Noticed that Go implementation does some caching, at least when converting public key to C version, so updated benchmarks to try to eliminate/reduce any caching benefits. Didn't find any noticeable difference so: public class Secp256r1VerifyBenchmarks
{
public byte[][] Inputs;
public string[] InputsHexes;
private ECDsa _ecdsaShared;
private X9ECParameters _bouncyCurve;
private ISigner _bouncySigner;
private ECDomainParameters _bouncyDomainParameters;
[Params(1, 100, 10_000)]
public int SamplesCount;
[GlobalSetup]
public void Setup()
{
_ecdsaShared = ECDsa.Create();
_bouncyCurve = CustomNamedCurves.GetByName("secp256r1");
_bouncySigner = SignerUtilities.GetSigner("NONEWITHPLAIN-ECDSA");
_bouncyDomainParameters = new(_bouncyCurve);
var rng = RandomNumberGenerator.Create();
Inputs = new byte[SamplesCount][];
InputsHexes = new string[SamplesCount];
for (var i = 0; i < Inputs.Length; i++)
{
var hash = new byte[32];
rng.GetBytes(hash);
var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
ECParameters pub = ecdsa.ExportParameters(false);
byte[] sig = ecdsa.SignHash(hash, DSASignatureFormat.IeeeP1363FixedFieldConcatenation);
(byte[] x, byte[] y) = (pub.Q.X, pub.Q.Y);
Inputs[i] = [..hash, ..sig, ..x, ..y];
InputsHexes[i] = Convert.ToHexString(Inputs[i]);
}
}
private int Index => Random.Shared.Next(Inputs.Length);
private byte[] Input => Inputs[Index];
private string InputHex => InputsHexes[Index];
[Benchmark]
public bool DotNet_Ecdsa_WithCreate()
{
ReadOnlySpan<byte> input = Input;
ReadOnlySpan<byte> hash = input[..32];
ReadOnlySpan<byte> sig = input[32..96];
ReadOnlySpan<byte> x = input[96..128];
ReadOnlySpan<byte> y = input[128..160];
var ecdsa = ECDsa.Create(new ECParameters
{
Curve = ECCurve.NamedCurves.nistP256,
Q = new() { X = x.ToArray(), Y = y.ToArray() }
});
return ecdsa.VerifyHash(hash, sig);
}
[Benchmark]
public bool DotNet_Ecdsa_WithImportParameters()
{
ReadOnlySpan<byte> input = Input;
ReadOnlySpan<byte> hash = input[..32];
ReadOnlySpan<byte> sig = input[32..96];
ReadOnlySpan<byte> x = input[96..128];
ReadOnlySpan<byte> y = input[128..160];
ECDsa ecdsa = _ecdsaShared;
ecdsa.ImportParameters(new ECParameters
{
Curve = ECCurve.NamedCurves.nistP256,
Q = new() { X = x.ToArray(), Y = y.ToArray() }
});
return ecdsa.VerifyHash(hash, sig);
}
[Benchmark]
public bool BouncyCastle()
{
ReadOnlySpan<byte> input = Input;
ReadOnlySpan<byte> hash = input[..32];
ReadOnlySpan<byte> sig = input[32..96];
ReadOnlySpan<byte> x = input[96..128];
ReadOnlySpan<byte> y = input[128..160];
ECPublicKeyParameters parameters = new(
"ECDSA",
_bouncyCurve.Curve.CreatePoint(new Org.BouncyCastle.Math.BigInteger(1, x), new Org.BouncyCastle.Math.BigInteger(1, y)),
_bouncyDomainParameters
);
_bouncySigner.Init(false, parameters);
_bouncySigner.BlockUpdate(hash);
return _bouncySigner.VerifySignature(sig.ToArray());
}
#if Windows
[DllImport("secp256r1.dll", CallingConvention = CallingConvention.Cdecl)]
#elif Linux
[DllImport("secp256r1.so", CallingConvention = CallingConvention.Cdecl)]
#endif
private static extern byte VerifyHex(string hash);
[Benchmark]
public bool Go_Ecdsa_MarshallingString() => VerifyHex(InputHex) != 0;
#if Windows
[DllImport("secp256r1.dll", CallingConvention = CallingConvention.Cdecl)]
#elif Linux
[DllImport("secp256r1.so", CallingConvention = CallingConvention.Cdecl)]
#endif
private static extern byte VerifyBytes(GoSlice hash);
private struct GoSlice(IntPtr data, long len, long cap)
{
public IntPtr Data = data;
public long Len = len, Cap = cap;
}
[Benchmark]
public bool Go_Ecdsa_MarshallingSlice()
{
ReadOnlySpan<byte> input = Input;
unsafe
{
fixed (byte* p = input)
{
var ptr = (IntPtr) p;
var slice = new GoSlice(ptr, input.Length, input.Length);
return VerifyBytes(slice) != 0;
}
}
}
// Other options:
// https://github.com/starkbank/ecdsa-dotnet - needs bridge/pinvoke from Rust
// https://github.com/MystenLabs/fastcrypto - very slow actually
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Changes
Adds secp256r1 precompile contract.
For now intended to be used in Optimism, but may be enabled in Ethereum as agreed.
EIP | RIP | Discussion | Geth PR
Types of changes
What types of changes does your code introduce?
Testing
Requires testing
If yes, did you write tests?
Requires explanation in Release Notes